{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "view-in-github"
   },
   "source": [
    "<a href=\"https://colab.research.google.com/github/tomasonjo/blogs/blob/master/Lord_of_the_wikidata/Part1%20Importing%20Wikidata%20into%20Neo4j%20and%20analyzing%20family%20trees.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "Hwk0pHemHeWt"
   },
   "source": [
    "* Updated to GDS 2.0 version\n",
    "* Link to original blog post: https://towardsdatascience.com/lord-of-the-wiki-ring-importing-wikidata-into-neo4j-and-analyzing-family-trees-da27f64d675e"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "8aH7cn62Hn3g",
    "outputId": "8625415d-c9c4-4e3c-85d9-9c3ae3055c43"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Collecting neo4j\n",
      "  Downloading neo4j-4.4.2.tar.gz (89 kB)\n",
      "\u001b[?25l\r",
      "\u001b[K     |███▋                            | 10 kB 24.3 MB/s eta 0:00:01\r",
      "\u001b[K     |███████▎                        | 20 kB 14.7 MB/s eta 0:00:01\r",
      "\u001b[K     |███████████                     | 30 kB 10.6 MB/s eta 0:00:01\r",
      "\u001b[K     |██████████████▋                 | 40 kB 9.2 MB/s eta 0:00:01\r",
      "\u001b[K     |██████████████████▎             | 51 kB 4.6 MB/s eta 0:00:01\r",
      "\u001b[K     |██████████████████████          | 61 kB 5.4 MB/s eta 0:00:01\r",
      "\u001b[K     |█████████████████████████▋      | 71 kB 5.8 MB/s eta 0:00:01\r",
      "\u001b[K     |█████████████████████████████▎  | 81 kB 5.7 MB/s eta 0:00:01\r",
      "\u001b[K     |████████████████████████████████| 89 kB 3.3 MB/s \n",
      "\u001b[?25hRequirement already satisfied: pytz in /usr/local/lib/python3.7/dist-packages (from neo4j) (2018.9)\n",
      "Building wheels for collected packages: neo4j\n",
      "  Building wheel for neo4j (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
      "  Created wheel for neo4j: filename=neo4j-4.4.2-py3-none-any.whl size=115365 sha256=1263c50dc3a5bf370b9d96525936014a00881f6b44f5d41da992312297966fac\n",
      "  Stored in directory: /root/.cache/pip/wheels/10/d6/28/95029d7f69690dbc3b93e4933197357987de34fbd44b50a0e4\n",
      "Successfully built neo4j\n",
      "Installing collected packages: neo4j\n",
      "Successfully installed neo4j-4.4.2\n"
     ]
    }
   ],
   "source": [
    "!pip install neo4j"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "l9PMy6mJHpvZ"
   },
   "source": [
    "I recommend you setup a [blank project on Neo4j Sandbox environment](https://sandbox.neo4j.com/?usecase=blank-sandbox), but you can also use other environment versions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "id": "IDQFrF1OHa4C"
   },
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "# Define Neo4j connections\n",
    "from neo4j import GraphDatabase\n",
    "host = 'bolt://44.193.28.203:7687'\n",
    "user = 'neo4j'\n",
    "password = 'combatants-coordinates-tugs'\n",
    "driver = GraphDatabase.driver(host,auth=(user, password))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "id": "OyQ7nwshHa4G"
   },
   "outputs": [],
   "source": [
    "# Import libraries\n",
    "import pandas as pd\n",
    "\n",
    "def run_query(query, params={}):\n",
    "    with driver.session() as session:\n",
    "        result = session.run(query, params)\n",
    "        return pd.DataFrame([r.values() for r in result], columns=result.keys())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 49
    },
    "id": "Yox2IsDmNuD3",
    "outputId": "88ff6dba-15f2-4293-a5a3-91eef0c8ef06"
   },
   "outputs": [],
   "source": [
    "# Fix default timeout query setting in Sandbox\n",
    "\n",
    "run_query(\"\"\"\n",
    "CALL dbms.setConfigValue('dbms.transaction.timeout','0')\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "g_gvwAYQHa4H"
   },
   "source": [
    "## Agenda\n",
    "\n",
    "* Import Wikipedia data to Neo4j\n",
    "* Basic graph exploration\n",
    "* Populate missing value\n",
    "* Some more graph exploration\n",
    "* Weakly connected component\n",
    "* Betweenness centrality\n",
    "\n",
    "We have been using simple graph schemas for quite some time now. I am delighted to say that this time we have a bit more complicated schema. The graph schema revolves around the characters in the LOTR world. A character can be either a relative, father, mother, enemy, spouse, or sibling with another character. This represents a social network of characters with multiple types of relationships. We also have additional information about characters such as their race, country, and language. On top of that, we also know if they are part of any group or have participated in any event.\n",
    "\n",
    "## WikiData import\n",
    "\n",
    "As mentioned, we will fetch the data from the WikiData API with the help of the apoc.load.json procedure. If you don't know yet, APOC provides great support for importing data into Neo4j. Besides the ability to fetch data from any REST API, it also features integrations with other databases such as MongoDB or relational databases via the JDBC driver.\n",
    "\n",
    "P.s. You should check out Neosematics library if you work a lot with RDF data, I only noticed it after I have written the post\n",
    "\n",
    "We will start by importing all the races in the LOTR world. I have to admit I am a total noob when it comes to SPARQL, so I won't be explaining the syntax in depth. If you need a basic introduction on how to query WikiData, I suggest this tutorial on Youtube. Basically, all the races in the LOTR world are an instance of the Middle-earth races entity with id Q989255. To get the instances of a specific entity, we use the following SPARQL clause:\n",
    "\n",
    "<code>?item wdt:P31 wd:Q989255</code>\n",
    "\n",
    "This can be translated as \"We would like to fetch an item, which is an instance of (wdt:P31) an entity with an id Q989255\". After we have downloaded the data with APOC, we store the results to Neo4j."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "id": "6t_-UwQ4Ha4J"
   },
   "outputs": [],
   "source": [
    "import_races_query = \"\"\"\n",
    "\n",
    "// Prepare a SPARQL query \n",
    "WITH 'SELECT ?item ?itemLabel WHERE{ ?item wdt:P31 wd:Q989255 . SERVICE wikibase:label { bd:serviceParam wikibase:language \"[AUTO_LANGUAGE],en\" }}' AS sparql \n",
    "// make a request to Wikidata\n",
    "CALL apoc.load.jsonParams('https://query.wikidata.org/sparql?query=' + \n",
    "                           apoc.text.urlencode(sparql), \n",
    "                         { Accept: \"application/sparql-results+json\"}, null) \n",
    "YIELD value \n",
    "// Unwind results to row \n",
    "UNWIND value['results']['bindings'] as row \n",
    "// Prepare data \n",
    "WITH row['itemLabel']['value'] as race, \n",
    "     row['item']['value'] as url, \n",
    "     split(row['item']['value'],'/')[-1] as id \n",
    "// Store to Neo4j \n",
    "CREATE (r:Race) SET r.race = race, \n",
    "                    r.url = url, \n",
    "                    r.id = id\n",
    "\n",
    "\"\"\"\n",
    "\n",
    "r = run_query(import_races_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "SJSd4ON_Ha4L"
   },
   "source": [
    "That was easy. The next step is to fetch the characters that are an instance of a given Middle-earth race. The SPARQL syntax is almost identical to the previous query, except this time we iterate over each race and find the characters that are an instance of a given race."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "id": "Q6d0zuc7Ha4L"
   },
   "outputs": [],
   "source": [
    "import_characters_query = \"\"\"\n",
    "\n",
    "// Iterate over each race in graph\n",
    "MATCH (r:Race)\n",
    "// Prepare a SparQL query\n",
    "WITH 'SELECT ?item ?itemLabel WHERE { ?item wdt:P31 wd:' + r.id + ' . SERVICE wikibase:label { bd:serviceParam wikibase:language \"[AUTO_LANGUAGE],en\" } }' AS sparql, r \n",
    "// make a request to Wikidata \n",
    "CALL apoc.load.jsonParams( \"https://query.wikidata.org/sparql?query=\" + \n",
    "                            apoc.text.urlencode(sparql), \n",
    "                            { Accept: \"application/sparql-results+json\"}, null)\n",
    "YIELD value \n",
    "UNWIND value['results']['bindings'] as row \n",
    "WITH row['itemLabel']['value'] as name, \n",
    "     row['item']['value'] as url, \n",
    "     split(row['item']['value'],'/')[-1] as id, r \n",
    "// Store to Neo4j \n",
    "CREATE (c:Character) \n",
    "SET c.name = name, \n",
    "    c.url = url, \n",
    "    c.id = id \n",
    "CREATE (c)-[:BELONG_TO]->(r)\n",
    "\n",
    "\"\"\"\n",
    "\n",
    "r = run_query(import_characters_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "m3vghrJ-Ha4M"
   },
   "source": [
    "Did you know that there are at least 700 characters in the Middle-earth world? I would never guess there would be so many documented characters on WikiData. Our first exploratory cypher query will be to count the characters by race."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 363
    },
    "id": "QZYwABSvHa4N",
    "outputId": "c0598e39-6598-4d63-9fd6-b57902e9b876"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>race</th>\n",
       "      <th>members</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>Middle-earth man</td>\n",
       "      <td>354</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>Hobbit</td>\n",
       "      <td>150</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>Middle-earth elf</td>\n",
       "      <td>86</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>Middle-earth dwarf</td>\n",
       "      <td>52</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>Valar</td>\n",
       "      <td>16</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>half-elven</td>\n",
       "      <td>11</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>Maiar</td>\n",
       "      <td>10</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>Orcs in Tolkien's legendarium</td>\n",
       "      <td>9</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>Ent</td>\n",
       "      <td>5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>dragons of Middle-earth</td>\n",
       "      <td>3</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                            race  members\n",
       "0               Middle-earth man      354\n",
       "1                         Hobbit      150\n",
       "2               Middle-earth elf       86\n",
       "3             Middle-earth dwarf       52\n",
       "4                          Valar       16\n",
       "5                     half-elven       11\n",
       "6                          Maiar       10\n",
       "7  Orcs in Tolkien's legendarium        9\n",
       "8                            Ent        5\n",
       "9        dragons of Middle-earth        3"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "race_size_query = \"\"\"\n",
    "\n",
    "MATCH (r:Race) \n",
    "RETURN r.race as race, \n",
    "       count{ (r)<-[:BELONG_TO]-() } as members \n",
    "ORDER BY members DESC \n",
    "LIMIT 10\n",
    "\n",
    "\"\"\"\n",
    "\n",
    "run_query(race_size_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "UazugeKjHa4N"
   },
   "source": [
    "The Fellowship of the Ring group is a somewhat representative sample of races in the Middle-earth. Most of the characters are either human or hobbits, with a couple of elves and dwarves strolling by. This is the first time I have heard of Valar and Maiar races though.\n",
    "\n",
    "Now it is time to enrich the graph with information about characters' gender, country, and manner of death. The SPARQL query will be a bit different than before. This time we will select a WikiData entity directly by its unique id and optionally fetch some of its properties. We can filter a specific entity by its id using the following SPARQL clause:\n",
    "\n",
    "<code>filter (?item = wd:' + r.id + ')</code>\n",
    "\n",
    "Similar to the cypher query language, SPARQL also differentiates between a MATCH and an OPTIONAL MATCH. When we want to return multiple properties of an entity, it is best to wrap each property into an OPTIONAL MATCH. This way we will get results if any of the properties exist. Without the OPTIONAL MATCH, we would only get results for entities where all three properties exist. This is an identical behavior to cypher.\n",
    "<code>\n",
    "OPTIONAL{ ?item wdt:P21 [rdfs:label ?gender] . \n",
    "           filter (lang(?gender)=\"en\") }\n",
    "</code>\n",
    "The <code>wdt:P21</code> indicates we are interested in the gender property.  We also specify that we want to get the English label of an entity instead of its WikiData id. The easiest way to search for the desired property id is to inspect the entity on the WikiData web page and hover over a property name.\n",
    "\n",
    "Another way is to use the WikiData query editor, which has a great autocomplete function by using the CTRL+T command.\n",
    "\n",
    "To store the results back to Neo4j we will use the <code>FOREACH</code> trick. Because some of our results will contain null values, we have to wrap the <code>MERGE</code> statement into the <code>FOREACH</code> statement which supports conditional execution. Check the Tips and tricks blog post by Michael Hunger for more information."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "id": "l3lj59OdHa4O"
   },
   "outputs": [],
   "source": [
    "import_gender_query = \"\"\"\n",
    "\n",
    "// Iterate over characters \n",
    "MATCH (r:Character) \n",
    "// Prepare a SparQL query \n",
    "WITH 'SELECT * WHERE{ ?item rdfs:label ?name . filter (?item = wd:' + r.id + ') filter (lang(?name) = \"en\" ) . ' +\n",
    "     'OPTIONAL{ ?item wdt:P21 [rdfs:label ?gender] . filter (lang(?gender)=\"en\") } ' + \n",
    "     'OPTIONAL{ ?item wdt:P27 [rdfs:label ?country] . filter (lang(?country)=\"en\") } ' +\n",
    "     'OPTIONAL{ ?item wdt:P1196 [rdfs:label ?death] . filter (lang(?death)=\"en\") }}' AS sparql, r \n",
    "// make a request to Wikidata \n",
    "CALL apoc.load.jsonParams( \"https://query.wikidata.org/sparql?query=\" \n",
    "    + apoc.text.urlencode(sparql), \n",
    "    { Accept: \"application/sparql-results+json\"}, null)\n",
    "YIELD value \n",
    "UNWIND value['results']['bindings'] as row \n",
    "SET r.gender = row['gender']['value'], \n",
    "    r.manner_of_death = row['death']['value'] \n",
    "// Execute FOREACH statement \n",
    "FOREACH(ignoreme in case when row['country'] is not null then [1] else [] end | \n",
    "    MERGE (c:Country{name:row['country']['value']}) \n",
    "    MERGE (r)-[:IN_COUNTRY]->(c))\n",
    "\n",
    "\"\"\"\n",
    "\n",
    "r = run_query(import_gender_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "GIpG7UxcHa4P"
   },
   "source": [
    "We are connecting additional information to our graph bit by bit and slowly transforming it into a knowledge graph. Let's first look at the manner of death property."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 143
    },
    "id": "f5s0jWxQHa4P",
    "outputId": "4a7b9491-6932-4644-ba34-b0413c21bcf4"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "  <div id=\"df-6872bea8-596a-46a2-a485-1bcb8fa875d9\">\n",
       "    <div class=\"colab-df-container\">\n",
       "      <div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>manner_of_death</th>\n",
       "      <th>count</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>homicide</td>\n",
       "      <td>3</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>death in battle</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>accident</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>\n",
       "      <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-6872bea8-596a-46a2-a485-1bcb8fa875d9')\"\n",
       "              title=\"Convert this dataframe to an interactive table.\"\n",
       "              style=\"display:none;\">\n",
       "        \n",
       "  <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
       "       width=\"24px\">\n",
       "    <path d=\"M0 0h24v24H0V0z\" fill=\"none\"/>\n",
       "    <path d=\"M18.56 5.44l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94zm-11 1L8.5 8.5l.94-2.06 2.06-.94-2.06-.94L8.5 2.5l-.94 2.06-2.06.94zm10 10l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94z\"/><path d=\"M17.41 7.96l-1.37-1.37c-.4-.4-.92-.59-1.43-.59-.52 0-1.04.2-1.43.59L10.3 9.45l-7.72 7.72c-.78.78-.78 2.05 0 2.83L4 21.41c.39.39.9.59 1.41.59.51 0 1.02-.2 1.41-.59l7.78-7.78 2.81-2.81c.8-.78.8-2.07 0-2.86zM5.41 20L4 18.59l7.72-7.72 1.47 1.35L5.41 20z\"/>\n",
       "  </svg>\n",
       "      </button>\n",
       "      \n",
       "  <style>\n",
       "    .colab-df-container {\n",
       "      display:flex;\n",
       "      flex-wrap:wrap;\n",
       "      gap: 12px;\n",
       "    }\n",
       "\n",
       "    .colab-df-convert {\n",
       "      background-color: #E8F0FE;\n",
       "      border: none;\n",
       "      border-radius: 50%;\n",
       "      cursor: pointer;\n",
       "      display: none;\n",
       "      fill: #1967D2;\n",
       "      height: 32px;\n",
       "      padding: 0 0 0 0;\n",
       "      width: 32px;\n",
       "    }\n",
       "\n",
       "    .colab-df-convert:hover {\n",
       "      background-color: #E2EBFA;\n",
       "      box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
       "      fill: #174EA6;\n",
       "    }\n",
       "\n",
       "    [theme=dark] .colab-df-convert {\n",
       "      background-color: #3B4455;\n",
       "      fill: #D2E3FC;\n",
       "    }\n",
       "\n",
       "    [theme=dark] .colab-df-convert:hover {\n",
       "      background-color: #434B5C;\n",
       "      box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
       "      filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
       "      fill: #FFFFFF;\n",
       "    }\n",
       "  </style>\n",
       "\n",
       "      <script>\n",
       "        const buttonEl =\n",
       "          document.querySelector('#df-6872bea8-596a-46a2-a485-1bcb8fa875d9 button.colab-df-convert');\n",
       "        buttonEl.style.display =\n",
       "          google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
       "\n",
       "        async function convertToInteractive(key) {\n",
       "          const element = document.querySelector('#df-6872bea8-596a-46a2-a485-1bcb8fa875d9');\n",
       "          const dataTable =\n",
       "            await google.colab.kernel.invokeFunction('convertToInteractive',\n",
       "                                                     [key], {});\n",
       "          if (!dataTable) return;\n",
       "\n",
       "          const docLinkHtml = 'Like what you see? Visit the ' +\n",
       "            '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
       "            + ' to learn more about interactive tables.';\n",
       "          element.innerHTML = '';\n",
       "          dataTable['output_type'] = 'display_data';\n",
       "          await google.colab.output.renderOutput(dataTable, element);\n",
       "          const docLink = document.createElement('div');\n",
       "          docLink.innerHTML = docLinkHtml;\n",
       "          element.appendChild(docLink);\n",
       "        }\n",
       "      </script>\n",
       "    </div>\n",
       "  </div>\n",
       "  "
      ],
      "text/plain": [
       "   manner_of_death  count\n",
       "0         homicide      3\n",
       "1  death in battle      1\n",
       "2         accident      1"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "manner_of_death_query = \"\"\"\n",
    "\n",
    "MATCH (n:Character) \n",
    "WHERE n.manner_of_death IS NOT NULL \n",
    "RETURN n.manner_of_death as manner_of_death, \n",
    "       count(*) as count\n",
    "\n",
    "\"\"\"\n",
    "\n",
    "run_query(manner_of_death_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "vBKeyjrFHa4P"
   },
   "source": [
    "Nothing of interest. This is obviously not the Game of Thrones series. Let's also inspect the results of the country property."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 363
    },
    "id": "H-OtGtHLHa4Q",
    "outputId": "ca396601-a14c-4cb6-def0-4ba1769eb837"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>country</th>\n",
       "      <th>members</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>Gondor</td>\n",
       "      <td>70</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>Shire</td>\n",
       "      <td>48</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>Rohan</td>\n",
       "      <td>34</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>Númenor</td>\n",
       "      <td>34</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>Arthedain</td>\n",
       "      <td>16</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>Arnor</td>\n",
       "      <td>8</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>Doriath</td>\n",
       "      <td>5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>Reunited Kingdom</td>\n",
       "      <td>3</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>Lothlórien</td>\n",
       "      <td>3</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>Gondolin</td>\n",
       "      <td>3</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "            country  members\n",
       "0            Gondor       70\n",
       "1             Shire       48\n",
       "2             Rohan       34\n",
       "3           Númenor       34\n",
       "4         Arthedain       16\n",
       "5             Arnor        8\n",
       "6           Doriath        5\n",
       "7  Reunited Kingdom        3\n",
       "8        Lothlórien        3\n",
       "9          Gondolin        3"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "country_info_query = \"\"\"\n",
    "\n",
    "MATCH (c:Country)\n",
    "RETURN c.name as country, \n",
    "       count{ (c)<-[:IN_COUNTRY]-() } as members\n",
    "ORDER BY members DESC \n",
    "LIMIT 10\n",
    "\n",
    "\"\"\"\n",
    "\n",
    "run_query(country_info_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "Bvplf6CEHa4Q"
   },
   "source": [
    "We have the country information for 236 characters. We could make some hypotheses and try to populate missing country values. Let's assume that if two characters are siblings, they belong to the same country. This makes a lot of sense. To be able to achieve this, we have to import the familial ties from WikiData. Specifically, we will fetch the father, mother, relative, sibling, and spouse connections."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "id": "BIqDirK8Ha4Q"
   },
   "outputs": [],
   "source": [
    "import_social_query = \"\"\"\n",
    "\n",
    "// Iterate over characters \n",
    "MATCH (r:Character) \n",
    "WITH 'SELECT * WHERE{ ?item rdfs:label ?name . filter (?item = wd:' + r.id + ') filter (lang(?name) = \"en\" ) . ' + \n",
    "     'OPTIONAL{ ?item wdt:P22 ?father } OPTIONAL{ ?item wdt:P25 ?mother } OPTIONAL{ ?item wdt:P1038 ?relative } ' +\n",
    "     'OPTIONAL{ ?item wdt:P3373 ?sibling } OPTIONAL{ ?item wdt:P26 ?spouse }}' AS sparql, r \n",
    "// make a request to wikidata \n",
    "CALL apoc.load.jsonParams( \"https://query.wikidata.org/sparql?query=\" + \n",
    "    apoc.text.urlencode(sparql), \n",
    "    { Accept: \"application/sparql-results+json\"}, null) YIELD value \n",
    "UNWIND value['results']['bindings'] as row \n",
    "FOREACH(ignoreme in case when row['mother'] is not null then [1] else [] end | \n",
    "    MERGE (c:Character{url:row['mother']['value']}) \n",
    "    MERGE (r)-[:HAS_MOTHER]->(c)) \n",
    "FOREACH(ignoreme in case when row['father'] is not null then [1] else [] end | \n",
    "    MERGE (c:Character{url:row['father']['value']}) \n",
    "    MERGE (r)-[:HAS_FATHER]->(c)) \n",
    "FOREACH(ignoreme in case when row['relative'] is not null then [1] else [] end | \n",
    "    MERGE (c:Character{url:row['relative']['value']}) \n",
    "    MERGE (r)-[:HAS_RELATIVE]-(c)) \n",
    "FOREACH(ignoreme in case when row['sibling'] is not null then [1] else [] end | \n",
    "    MERGE (c:Character{url:row['sibling']['value']}) \n",
    "    MERGE (r)-[:SIBLING]-(c))\n",
    "FOREACH(ignoreme in case when row['spouse'] is not null then [1] else [] end | \n",
    "    MERGE (c:Character{url:row['spouse']['value']}) \n",
    "    MERGE (r)-[:SPOUSE]-(c))\n",
    "\n",
    "\"\"\"\n",
    "\n",
    "r = run_query(import_social_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "I0qIuaSYHa4R"
   },
   "source": [
    "Before we begin filling-in missing values, let's check for promiscuity in the Middle-earth. The first query will search for characters with multiple spouses."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 112
    },
    "id": "imDtdPM7Ha4R",
    "outputId": "1d605d05-14e1-483b-b115-8d2040bc7ae6"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>result</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>[Indis, Finwë, Míriel]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>[Míriel, Finwë, Indis]</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                   result\n",
       "0  [Indis, Finwë, Míriel]\n",
       "1  [Míriel, Finwë, Indis]"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "multiple_spouses_query = \"\"\"\n",
    "\n",
    "MATCH p=(a)-[:SPOUSE]-(b)-[:SPOUSE]-(c) \n",
    "RETURN [n IN nodes(p) | n.name] AS result LIMIT 10\n",
    "\n",
    "\"\"\"\n",
    "\n",
    "run_query(multiple_spouses_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "NdC3zUP0Ha4R"
   },
   "source": [
    "We actually found a single character with two spouses. It is Finwë, the first King of the Noldor. We can also take a look if someone has kids with multiple partners"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 206
    },
    "id": "AFYIBdk8Ha4S",
    "outputId": "b20d060a-9dc0-4fad-c808-387ea40f5151"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>result</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>[Finwë, Fingolfin, Indis]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>[Finwë, Findis, Indis]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>[Finwë, Irimë, Indis]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>[Finwë, Finarfin, Indis]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>[Finwë, Fëanor, Míriel]</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                      result\n",
       "0  [Finwë, Fingolfin, Indis]\n",
       "1     [Finwë, Findis, Indis]\n",
       "2      [Finwë, Irimë, Indis]\n",
       "3   [Finwë, Finarfin, Indis]\n",
       "4    [Finwë, Fëanor, Míriel]"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "multiple_kids_query = \"\"\"\n",
    "\n",
    "MATCH (c:Character)<-[:HAS_FATHER|HAS_MOTHER]-()-[:HAS_FATHER|HAS_MOTHER]->(other) \n",
    "WITH c, collect(distinct other) as others \n",
    "WHERE size(others) > 1 \n",
    "MATCH p=(c)<-[:HAS_FATHER|HAS_MOTHER]-()-[:HAS_FATHER|HAS_MOTHER]->() \n",
    "RETURN [n IN nodes(p) | n.name] AS result LIMIT 10\n",
    "\n",
    "\"\"\"\n",
    "\n",
    "run_query(multiple_kids_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "YcfTme1qHa4S"
   },
   "source": [
    "So it seems that Finwë has four children with Indis and a single child with Míriel. On the other hand, it is quite weird that Beren has two fathers. I guess Adanel has some explaining to do. We would probably find more death and promiscuity in the GoT world.\n",
    "\n",
    "## Populate missing values\n",
    "\n",
    "Now that we know that the Middle-earth characters abstain from promiscuity, let's populate the missing country values. Remember our hypothesis was:\n",
    "\n",
    ">If two characters are siblings, they belong to the same country.\n",
    "\n",
    "Before we populate the missing values for countries, let's populate the missing values for siblings. We will assume that if two characters have the same mother or father, they are siblings. Let's look at some sibling candidates."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 363
    },
    "id": "XWdkChq-Ha4T",
    "outputId": "7e511e96-9487-4c73-b69e-9e85dca43890"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>result</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>[Ferumbras Took II, Isumbras Took III, Bandobr...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>[Linda Proudfoot, Mungo Baggins, Bungo Baggins]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>[Bingo Baggins, Mungo Baggins, Bungo Baggins]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>[Belba Baggins, Mungo Baggins, Bungo Baggins]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>[Belba Baggins, Laura Grubb, Bungo Baggins]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>[Linda Proudfoot, Laura Grubb, Bungo Baggins]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>[Bingo Baggins, Laura Grubb, Bungo Baggins]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>[Isembold Took, Adamanta Chubb, Isembard Took]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>[Isengar Took, Adamanta Chubb, Isembard Took]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>[Donnamira Took, Adamanta Chubb, Isembard Took]</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                              result\n",
       "0  [Ferumbras Took II, Isumbras Took III, Bandobr...\n",
       "1    [Linda Proudfoot, Mungo Baggins, Bungo Baggins]\n",
       "2      [Bingo Baggins, Mungo Baggins, Bungo Baggins]\n",
       "3      [Belba Baggins, Mungo Baggins, Bungo Baggins]\n",
       "4        [Belba Baggins, Laura Grubb, Bungo Baggins]\n",
       "5      [Linda Proudfoot, Laura Grubb, Bungo Baggins]\n",
       "6        [Bingo Baggins, Laura Grubb, Bungo Baggins]\n",
       "7     [Isembold Took, Adamanta Chubb, Isembard Took]\n",
       "8      [Isengar Took, Adamanta Chubb, Isembard Took]\n",
       "9    [Donnamira Took, Adamanta Chubb, Isembard Took]"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sibling_candidate_query = \"\"\"\n",
    "\n",
    "MATCH p=(a:Character)-[:HAS_FATHER|:HAS_MOTHER]->()<-[:HAS_FATHER|:HAS_MOTHER]-(b:Character) \n",
    "WHERE NOT exists { (a)-[:SIBLING]-(b) } \n",
    "RETURN [n IN nodes(p) | n.name] AS result LIMIT 10\n",
    "\n",
    "\"\"\"\n",
    "\n",
    "run_query(sibling_candidate_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "bHKPYakKHa4T"
   },
   "source": [
    "Adamanta Chubb has at least six children. Only two of them are marked as siblings. Because all of them are siblings by definition, we will fill in the missing connections."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 49
    },
    "id": "VHdXvvIHHa4T",
    "outputId": "f3095cf0-4496-41d5-eb6f-7a9e52d7ffb4"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "Empty DataFrame\n",
       "Columns: []\n",
       "Index: []"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sibling_populate_query = \"\"\"\n",
    "\n",
    "MATCH p=(a:Character)-[:HAS_FATHER|:HAS_MOTHER]->()<-[:HAS_FATHER|:HAS_MOTHER]-(b:Character) \n",
    "WHERE NOT exists { (a)-[:SIBLING]-(b) } \n",
    "MERGE (a)-[:SIBLING]-(b)\n",
    "\n",
    "\"\"\"\n",
    "run_query(sibling_populate_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "LsxV7gZJHa4T"
   },
   "source": [
    "The query added 118 missing relationships. I need to learn how to update the WikiData knowledge graph and add the missing relationships in bulk. Now we can fill in the missing country values for siblings. We will match all characters with the filled in country information and search for their siblings that don't have the country information. I love how easy it is to express this pattern with cypher query language."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 49
    },
    "id": "Ipj0d95THa4T",
    "outputId": "2d95cf28-d60a-487d-d51e-a8ce2a327ef6"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "Empty DataFrame\n",
       "Columns: []\n",
       "Index: []"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "country_populate_query = \"\"\"\n",
    "\n",
    "MATCH (country)<-[:IN_COUNTRY]-(s:Character)-[:SIBLING]-(t:Character) \n",
    "WHERE NOT exists { (t)-[:IN_COUNTRY]->() }\n",
    "MERGE (t)-[:IN_COUNTRY]->(country)\n",
    "\n",
    "\"\"\"\n",
    "run_query(country_populate_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "KDh1CjK3Ha4U"
   },
   "source": [
    "There were 49 missing countries added. We could easily come up with more hypotheses to fill in the missing values. You can try and maybe add some other missing values yourself.\n",
    "\n",
    "We still have to add some information to our graph. In this query, we will add the information about the occupation, language, groups, and events of characters. The SPARQL query is identical to before where we iterate over each character and fetch additional properties."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 49
    },
    "id": "3AVPww1fHa4U",
    "outputId": "faae2f85-abe9-4d2e-a795-9921abe21ce2"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "Empty DataFrame\n",
       "Columns: []\n",
       "Index: []"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import_groups_query = \"\"\"\n",
    "\n",
    "MATCH (r:Character) \n",
    "WHERE r.id IS NOT NULL \n",
    "WITH 'SELECT * WHERE{ ?item rdfs:label ?name . filter (?item = wd:' + r.id + ') filter (lang(?name) = \"en\" ) . ' +\n",
    "      'OPTIONAL { ?item wdt:P106 [rdfs:label ?occupation ] . filter (lang(?occupation) = \"en\" ). } ' +\n",
    "      'OPTIONAL { ?item wdt:P103 [rdfs:label ?language ] . filter (lang(?language) = \"en\" ) . } ' +\n",
    "      'OPTIONAL { ?item wdt:P463 [rdfs:label ?member_of ] . filter (lang(?member_of) = \"en\" ). } ' +\n",
    "      'OPTIONAL { ?item wdt:P1344[rdfs:label ?participant ] . filter (lang(?participant) = \"en\") . } ' +\n",
    "      'OPTIONAL { ?item wdt:P39[rdfs:label ?position ] . filter (lang(?position) = \"en\") . }}' AS sparql, r \n",
    "CALL apoc.load.jsonParams( \"https://query.wikidata.org/sparql?query=\" + \n",
    "                             apoc.text.urlencode(sparql), \n",
    "                             { Accept: \"application/sparql-results+json\"}, null) \n",
    "YIELD value \n",
    "UNWIND value['results']['bindings'] as row \n",
    "FOREACH(ignoreme in case when row['language'] is not null then [1] else [] end | \n",
    "        MERGE (c:Language{name:row['language']['value']}) \n",
    "        MERGE (r)-[:HAS_LANGUAGE]->(c)) \n",
    "FOREACH(ignoreme in case when row['occupation'] is not null then [1] else [] end | \n",
    "        MERGE (c:Occupation{name:row['occupation']['value']}) \n",
    "        MERGE (r)-[:HAS_OCCUPATION]->(c)) \n",
    "FOREACH(ignoreme in case when row['member_of'] is not null then [1] else [] end | \n",
    "        MERGE (c:Group{name:row['member_of']['value']}) \n",
    "        MERGE (r)-[:MEMBER_OF]->(c)) \n",
    "FOREACH(ignoreme in case when row['participant'] is not null then [1] else [] end | \n",
    "        MERGE (c:Event{name:row['participant']['value']}) \n",
    "        MERGE (r)-[:PARTICIPATED]->(c)) \n",
    "SET r.position = row['position']['value']\n",
    "\n",
    "\"\"\"\n",
    "run_query(import_groups_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "OVN0slGqHa4U"
   },
   "source": [
    "Let's investigate the results of the groups and the occupation of the characters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 300
    },
    "id": "NQsHXDmgHa4V",
    "outputId": "fefc55bc-65c1-4a95-c4ce-12afc8c53f12"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>group</th>\n",
       "      <th>size</th>\n",
       "      <th>members</th>\n",
       "      <th>occupations</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>Thorin and Company</td>\n",
       "      <td>14</td>\n",
       "      <td>[Glóin, Bofur, Thorin II]</td>\n",
       "      <td>[swordfighter, diarist]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>Fellowship of the Ring</td>\n",
       "      <td>12</td>\n",
       "      <td>[Gimli, Peregrin Took, Samwise Gamgee]</td>\n",
       "      <td>[swordfighter, domestic worker, gardener]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>White Council</td>\n",
       "      <td>3</td>\n",
       "      <td>[Elrond, Gandalf, Gandalf]</td>\n",
       "      <td>[magician, swordfighter]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>Rangers of Ithilien</td>\n",
       "      <td>2</td>\n",
       "      <td>[Damrod, Madril]</td>\n",
       "      <td>[]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>Union of Maedhros</td>\n",
       "      <td>2</td>\n",
       "      <td>[Haldir, Halmir]</td>\n",
       "      <td>[]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>Wise</td>\n",
       "      <td>2</td>\n",
       "      <td>[Adanel, Andreth]</td>\n",
       "      <td>[]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>Istari</td>\n",
       "      <td>2</td>\n",
       "      <td>[Gandalf, Gandalf]</td>\n",
       "      <td>[magician, swordfighter]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>White Company</td>\n",
       "      <td>1</td>\n",
       "      <td>[Beregond]</td>\n",
       "      <td>[guard]</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                    group  size                                 members  \\\n",
       "0      Thorin and Company    14               [Glóin, Bofur, Thorin II]   \n",
       "1  Fellowship of the Ring    12  [Gimli, Peregrin Took, Samwise Gamgee]   \n",
       "2           White Council     3              [Elrond, Gandalf, Gandalf]   \n",
       "3     Rangers of Ithilien     2                        [Damrod, Madril]   \n",
       "4       Union of Maedhros     2                        [Haldir, Halmir]   \n",
       "5                    Wise     2                       [Adanel, Andreth]   \n",
       "6                  Istari     2                      [Gandalf, Gandalf]   \n",
       "7           White Company     1                              [Beregond]   \n",
       "\n",
       "                                 occupations  \n",
       "0                    [swordfighter, diarist]  \n",
       "1  [swordfighter, domestic worker, gardener]  \n",
       "2                   [magician, swordfighter]  \n",
       "3                                         []  \n",
       "4                                         []  \n",
       "5                                         []  \n",
       "6                   [magician, swordfighter]  \n",
       "7                                    [guard]  "
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "investigate_groups_query = \"\"\"\n",
    "\n",
    "MATCH (n:Group)<-[:MEMBER_OF]-(c)\n",
    "OPTIONAL MATCH (c)-[:HAS_OCCUPATION]->(o) \n",
    "RETURN n.name as group, \n",
    "       count(*) as size, \n",
    "       collect(c.name)[..3] as members, \n",
    "       collect(distinct o.name)[..3] as occupations \n",
    "ORDER BY size DESC\n",
    "\n",
    "\"\"\"\n",
    "\n",
    "run_query(investigate_groups_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "nZGm5a1VHa4V"
   },
   "source": [
    "It was at this moment that I realized the whole Hobbit series are included. Balin was the diarist for the Thorin and Company group. For some reason, I was expecting Bilbo Baggins to be the diarist. Obviously, there can be only one archer in the Fellowship of the Ring group, and that is Legolas. Gandalf seems to be involved in a couple of groups.\n",
    "\n",
    "We will execute one more WikiData API call. This time we will fetch the enemies and the items the characters own."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "id": "iZ075XIuHa4V"
   },
   "outputs": [],
   "source": [
    "import_enemy_query = \"\"\"\n",
    "\n",
    "MATCH (r:Character) \n",
    "WHERE r.id IS NOT NULL \n",
    "WITH 'SELECT * WHERE { ?item rdfs:label ?name . filter (?item = wd:' + r.id + ') filter (lang(?name) = \"en\" ) . ' +\n",
    "      'OPTIONAL{ ?item wdt:P1830 [rdfs:label ?owner ] . filter (lang(?owner) = \"en\" ). } ' +\n",
    "      'OPTIONAL{ ?item wdt:P7047 ?enemy }}' AS sparql, r \n",
    "CALL apoc.load.jsonParams( \"https://query.wikidata.org/sparql?query=\" + \n",
    "                            apoc.text.urlencode(sparql), \n",
    "                            { Accept: \"application/sparql-results+json\"}, null) \n",
    "YIELD value \n",
    "WITH value,r \n",
    "WHERE value['results']['bindings'] <> [] \n",
    "UNWIND value['results']['bindings'] as row \n",
    "FOREACH(ignoreme in case when row['owner'] is not null then [1] else [] end |\n",
    "    MERGE (c:Item{name:row['owner']['value']}) \n",
    "    MERGE (r)-[:OWNS_ITEM]->(c)) \n",
    "FOREACH(ignoreme in case when row['enemy'] is not null then [1] else [] end | \n",
    "    MERGE (c:Character{url:row['enemy']['value']}) \n",
    "    MERGE (r)-[:ENEMY]->(c))\n",
    "\n",
    "\"\"\"\n",
    "\n",
    "r = run_query(import_enemy_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "IGRptZDwHa4W"
   },
   "source": [
    "Finally, we have finished importing our graph. Let's look at how many enemies are there between direct family members."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 112
    },
    "id": "lMs1wi0fHa4W",
    "outputId": "2d76e2b2-6d8f-472e-f92d-671a1539be0a"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>result</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>[Manwë, Morgoth]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>[Morgoth, Manwë]</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "             result\n",
       "0  [Manwë, Morgoth]\n",
       "1  [Morgoth, Manwë]"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "family_enemy_query = \"\"\"\n",
    "\n",
    "MATCH p=(a)-[:SPOUSE|SIBLING|HAS_FATHER|HAS_MOTHER]-(b) \n",
    "WHERE exists { (a)-[:ENEMY]-(b) } \n",
    "RETURN [n IN nodes(p) | n.name] AS result LIMIT 10\n",
    "\n",
    "\"\"\"\n",
    "run_query(family_enemy_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "m-s2T9ZIHa4W"
   },
   "source": [
    "It looks like Morgoth and Manwë are brothers and enemies. This is the first time I have heard of the two, but LOTR fandom site claims Morgoth was the first Dark Lord. Let's look at how many enemies are within the second-degree relatives."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 175
    },
    "id": "MiRNuFtyHa4W",
    "outputId": "5e5f5a11-2caf-4d39-e758-1e1bf4fed74c"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>result</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>[Manwë, Morgoth]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>[Morgoth, Manwë]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>[Morgoth, Manwë, Varda]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>[Varda, Manwë, Morgoth]</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                    result\n",
       "0         [Manwë, Morgoth]\n",
       "1         [Morgoth, Manwë]\n",
       "2  [Morgoth, Manwë, Varda]\n",
       "3  [Varda, Manwë, Morgoth]"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "family_enemy_2hops_query = \"\"\"\n",
    "\n",
    "MATCH p=(a)-[:SPOUSE|SIBLING|HAS_FATHER|HAS_MOTHER*..2]-(b) \n",
    "WHERE exists { (a)-[:ENEMY]-(b) } \n",
    "RETURN [n IN nodes(p) | n.name] AS result LIMIT 10\n",
    "\n",
    "\"\"\"\n",
    "run_query(family_enemy_2hops_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "vNIv7qiMHa4X"
   },
   "source": [
    "Not a lot of enemies within the second-degree relatives. We can observe that Varda has taken her husband's stance and is also an enemy with Morgoth. This is an example of a stable triangle or triad. The triangle consists of one positive relationship (SPOUSE) and two negatives (ENEMY). In social network analysis, triangles are used to measure the cohesiveness and structural stability of a network.\n",
    "\n",
    "## Graph data science\n",
    "\n",
    "If you have read any of my previous blog posts, you know that I just have to include some example use cases of graph algorithms from the Graph Data Science library. If you need a quick refresher on how the GDS library works and what is happening behind the scenes, I suggest you read my previous blog post.\n",
    "\n",
    "We will start by projecting the family network. We load all the characters and the familial relationships like SPOUSE, SIBLING, HAS_FATHER, and HAS_MOTHER between them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 81
    },
    "id": "ND_zVoYOHa4X",
    "outputId": "88bccf60-ff12-43a1-b663-570dfedfa92e"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>nodeProjection</th>\n",
       "      <th>relationshipProjection</th>\n",
       "      <th>graphName</th>\n",
       "      <th>nodeCount</th>\n",
       "      <th>relationshipCount</th>\n",
       "      <th>projectMillis</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>{'Character': {'label': 'Character', 'properti...</td>\n",
       "      <td>{'HAS_MOTHER': {'orientation': 'NATURAL', 'ind...</td>\n",
       "      <td>family</td>\n",
       "      <td>710</td>\n",
       "      <td>1060</td>\n",
       "      <td>26</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                      nodeProjection  \\\n",
       "0  {'Character': {'label': 'Character', 'properti...   \n",
       "\n",
       "                              relationshipProjection graphName  nodeCount  \\\n",
       "0  {'HAS_MOTHER': {'orientation': 'NATURAL', 'ind...    family        710   \n",
       "\n",
       "   relationshipCount  projectMillis  \n",
       "0               1060             26  "
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "project_graph = \"\"\"\n",
    "CALL gds.graph.project('family','Character', \n",
    "    ['SPOUSE','SIBLING','HAS_FATHER','HAS_MOTHER'])\n",
    "\"\"\"\n",
    "run_query(project_graph)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "HUBD0xphHa4X"
   },
   "source": [
    "### Weakly connected component\n",
    "\n",
    "The weakly connected component algorithm is used to find islands or disconnected components within our network. The following visualizations contain two connected components. The first component is composed of Michael, Mark, and Doug while the second one consists of Alice, Charles, and Bridget.\n",
    "\n",
    "In our case, we will use the weakly connected component algorithm to find islands within the family network. All members within the same family component are related to each other somehow. Could be a cousin of the sister-in-law's grandmother or something more direct like a sibling. To get a rough feeling of the results, we will run the stats mode of the algorithm."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 81
    },
    "id": "3g8ufqCcHa4X",
    "outputId": "f70b07d5-ad38-49ff-8014-72a4461b3932"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>components</th>\n",
       "      <th>p75</th>\n",
       "      <th>p90</th>\n",
       "      <th>mean</th>\n",
       "      <th>max</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>156</td>\n",
       "      <td>1</td>\n",
       "      <td>3</td>\n",
       "      <td>4.55</td>\n",
       "      <td>321</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   components  p75  p90  mean  max\n",
       "0         156    1    3  4.55  321"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "wcc_stats_query = \"\"\"\n",
    "\n",
    "CALL gds.wcc.stats('family') \n",
    "YIELD componentCount, \n",
    "      componentDistribution \n",
    "RETURN componentCount as components, \n",
    "       componentDistribution.p75 as p75, \n",
    "       componentDistribution.p90 as p90, \n",
    "       round(componentDistribution.mean,2) as mean, \n",
    "       componentDistribution.max as max\n",
    "\n",
    "\"\"\"\n",
    "\n",
    "run_query(wcc_stats_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "nCgfAjCmHa4X"
   },
   "source": [
    "There are 145 connected components in our graph. More than 75% of the components contain only a single character. This means that around 110 (75% * 145) characters don't have a single familial link to any other character. If they had a single link, the size of the component would be at least two.  The biggest component has 328 members, so that must be one happy family. Let's write back the results and further analyze the family components."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 81
    },
    "id": "YDdRNi4EHa4Y",
    "outputId": "264f1bb5-1078-45ef-b89b-56d660269a50"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>writeMillis</th>\n",
       "      <th>nodePropertiesWritten</th>\n",
       "      <th>componentCount</th>\n",
       "      <th>componentDistribution</th>\n",
       "      <th>postProcessingMillis</th>\n",
       "      <th>preProcessingMillis</th>\n",
       "      <th>computeMillis</th>\n",
       "      <th>configuration</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>123</td>\n",
       "      <td>710</td>\n",
       "      <td>156</td>\n",
       "      <td>{'p99': 139, 'min': 1, 'max': 321, 'mean': 4.5...</td>\n",
       "      <td>9</td>\n",
       "      <td>0</td>\n",
       "      <td>4</td>\n",
       "      <td>{'jobId': '6c933782-d1a8-4f5e-9748-fad56cebab3...</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   writeMillis  nodePropertiesWritten  componentCount  \\\n",
       "0          123                    710             156   \n",
       "\n",
       "                               componentDistribution  postProcessingMillis  \\\n",
       "0  {'p99': 139, 'min': 1, 'max': 321, 'mean': 4.5...                     9   \n",
       "\n",
       "   preProcessingMillis  computeMillis  \\\n",
       "0                    0              4   \n",
       "\n",
       "                                       configuration  \n",
       "0  {'jobId': '6c933782-d1a8-4f5e-9748-fad56cebab3...  "
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "wcc_write_query = \"\"\"\n",
    "\n",
    "CALL gds.wcc.write('family', {writeProperty:'familyComponent'})\n",
    "\n",
    "\"\"\"\n",
    "\n",
    "run_query(wcc_write_query)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 81
    },
    "id": "x1EWzmFORYVy",
    "outputId": "5d433f56-7847-4b9e-e645-2d05b974c2fb"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>mutateMillis</th>\n",
       "      <th>nodePropertiesWritten</th>\n",
       "      <th>componentCount</th>\n",
       "      <th>componentDistribution</th>\n",
       "      <th>postProcessingMillis</th>\n",
       "      <th>preProcessingMillis</th>\n",
       "      <th>computeMillis</th>\n",
       "      <th>configuration</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0</td>\n",
       "      <td>710</td>\n",
       "      <td>156</td>\n",
       "      <td>{'p99': 139, 'min': 1, 'max': 321, 'mean': 4.5...</td>\n",
       "      <td>5</td>\n",
       "      <td>0</td>\n",
       "      <td>5</td>\n",
       "      <td>{'jobId': '7273e197-dde4-4aed-bf2f-7aebb547cca...</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   mutateMillis  nodePropertiesWritten  componentCount  \\\n",
       "0             0                    710             156   \n",
       "\n",
       "                               componentDistribution  postProcessingMillis  \\\n",
       "0  {'p99': 139, 'min': 1, 'max': 321, 'mean': 4.5...                     5   \n",
       "\n",
       "   preProcessingMillis  computeMillis  \\\n",
       "0                    0              5   \n",
       "\n",
       "                                       configuration  \n",
       "0  {'jobId': '7273e197-dde4-4aed-bf2f-7aebb547cca...  "
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Also need to mutate in order to be able to use subgraph later on\n",
    "\n",
    "wcc_mutate_query = \"\"\"\n",
    "\n",
    "CALL gds.wcc.mutate('family', {mutateProperty:'familyComponent'})\n",
    "\n",
    "\"\"\"\n",
    "\n",
    "run_query(wcc_mutate_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "EmTUYR0AHa4Y"
   },
   "source": [
    "We will start by looking at the top five largest family components. The first thing we are interested in is which races are present in the family trees. We'll also add some random members in the results to get a better feeling of the data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 206
    },
    "id": "awKFPFy7Ha4Y",
    "outputId": "df34a048-a6ac-4bfa-f059-ea2d030feb14"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>familyComponent</th>\n",
       "      <th>size</th>\n",
       "      <th>random_members</th>\n",
       "      <th>family_race</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0</td>\n",
       "      <td>321</td>\n",
       "      <td>[Galadriel, Fingolfin, Amras]</td>\n",
       "      <td>[Middle-earth elf, Maiar, Middle-earth man, ha...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>8</td>\n",
       "      <td>139</td>\n",
       "      <td>[Frodo Baggins, Bilbo Baggins, Samwise Gamgee]</td>\n",
       "      <td>[Hobbit]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>259</td>\n",
       "      <td>29</td>\n",
       "      <td>[Thorin II, Gimli, Balin]</td>\n",
       "      <td>[Middle-earth dwarf]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>378</td>\n",
       "      <td>21</td>\n",
       "      <td>[Cirion, Eradan, Belegorn]</td>\n",
       "      <td>[Middle-earth man]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>157</td>\n",
       "      <td>6</td>\n",
       "      <td>[Aulë, Oromë, Tulkas]</td>\n",
       "      <td>[Valar]</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   familyComponent  size                                  random_members  \\\n",
       "0                0   321                   [Galadriel, Fingolfin, Amras]   \n",
       "1                8   139  [Frodo Baggins, Bilbo Baggins, Samwise Gamgee]   \n",
       "2              259    29                       [Thorin II, Gimli, Balin]   \n",
       "3              378    21                      [Cirion, Eradan, Belegorn]   \n",
       "4              157     6                           [Aulë, Oromë, Tulkas]   \n",
       "\n",
       "                                         family_race  \n",
       "0  [Middle-earth elf, Maiar, Middle-earth man, ha...  \n",
       "1                                           [Hobbit]  \n",
       "2                               [Middle-earth dwarf]  \n",
       "3                                 [Middle-earth man]  \n",
       "4                                            [Valar]  "
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "top5_families_query = \"\"\"\n",
    "\n",
    "MATCH (c:Character) \n",
    "OPTIONAL MATCH (c)-[:BELONG_TO]->(race) \n",
    "WITH c.familyComponent as familyComponent, \n",
    "     count(*) as size, \n",
    "     collect(c.name) as members, \n",
    "     collect(distinct race.race) as family_race \n",
    "ORDER BY size DESC LIMIT 5 \n",
    "RETURN familyComponent, \n",
    "       size, \n",
    "       members[..3] as random_members, \n",
    "       family_race\n",
    "\"\"\"\n",
    "\n",
    "run_query(top5_families_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "68C5-8MQHa4Y"
   },
   "source": [
    "As mentioned, the largest family has 328 members of various races ranging from elves to humans and even Maiar. It appears that elven and human lifes are quite intertwined in the Middle-earth. Also their legs. There is a reason why the half-elven race even exists. Other races like hobbits and dwarves stick more to their own kind.\n",
    "\n",
    "Let's examine the interracial marriages in the largest community."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 238
    },
    "id": "sCbdf4g0Ha4Y",
    "outputId": "f7ea21ae-757d-41ac-c951-516678b7504f"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>spouse_1</th>\n",
       "      <th>race_1</th>\n",
       "      <th>spouse_2</th>\n",
       "      <th>race_2</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>Melian</td>\n",
       "      <td>Maiar</td>\n",
       "      <td>Thingol</td>\n",
       "      <td>Middle-earth elf</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>Dior Eluchíl</td>\n",
       "      <td>half-elven</td>\n",
       "      <td>Nimloth</td>\n",
       "      <td>Middle-earth elf</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>Beren</td>\n",
       "      <td>Middle-earth man</td>\n",
       "      <td>Lúthien</td>\n",
       "      <td>Middle-earth elf</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>Elrond</td>\n",
       "      <td>half-elven</td>\n",
       "      <td>Celebrían</td>\n",
       "      <td>Middle-earth elf</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>Tuor</td>\n",
       "      <td>Middle-earth man</td>\n",
       "      <td>Idril</td>\n",
       "      <td>Middle-earth elf</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>Arwen</td>\n",
       "      <td>half-elven</td>\n",
       "      <td>Aragorn</td>\n",
       "      <td>Middle-earth man</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "       spouse_1            race_1   spouse_2            race_2\n",
       "0        Melian             Maiar    Thingol  Middle-earth elf\n",
       "1  Dior Eluchíl        half-elven    Nimloth  Middle-earth elf\n",
       "2         Beren  Middle-earth man    Lúthien  Middle-earth elf\n",
       "3        Elrond        half-elven  Celebrían  Middle-earth elf\n",
       "4          Tuor  Middle-earth man      Idril  Middle-earth elf\n",
       "5         Arwen        half-elven    Aragorn  Middle-earth man"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ir_query = \"\"\"\n",
    "\n",
    "MATCH (c:Character) \n",
    "WHERE c.familyComponent = 0 // fix the family component \n",
    "MATCH p=(race)<-[:BELONG_TO]-(c)-[:SPOUSE]-(other)-[:BELONG_TO]->(other_race) \n",
    "WHERE race <> other_race AND id(c) > id(other) \n",
    "RETURN c.name as spouse_1, \n",
    "       race.race as race_1, \n",
    "       other.name as spouse_2, \n",
    "       other_race.race as race_2\n",
    "\"\"\"\n",
    "\n",
    "run_query(ir_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ax5hKMTLHa4Z"
   },
   "source": [
    "First of all, I didn't know that Elrond was a half-elf. It seems like the human and elven \"alliance\" is as old as time itself. I was mainly expecting to see Arwen and Aragorn as I remember that from the movies. It would be interesting to learn how far back do half-elves go. Let's look who are the half-elves with the most descendants."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 206
    },
    "id": "bXhIScloHa4Z",
    "outputId": "eddf3509-03a5-4907-b151-721e0040a67d"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>character</th>\n",
       "      <th>descendants</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>Dior Eluchíl</td>\n",
       "      <td>11</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>Eärendil</td>\n",
       "      <td>10</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>Elwing</td>\n",
       "      <td>10</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>Elros</td>\n",
       "      <td>9</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>Elrond</td>\n",
       "      <td>2</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "      character  descendants\n",
       "0  Dior Eluchíl           11\n",
       "1      Eärendil           10\n",
       "2        Elwing           10\n",
       "3         Elros            9\n",
       "4        Elrond            2"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "oldest_halfelf_query = \"\"\"\n",
    "\n",
    "MATCH (c:Character)\n",
    "WHERE exists{ (c)-[:BELONG_TO]->(:Race{race:'half-elven'}) }\n",
    "MATCH p=(c)<-[:HAS_FATHER|HAS_MOTHER*..20]-(end)\n",
    "WHERE NOT (end)<-[:HAS_FATHER|:HAS_MOTHER]-()\n",
    "WITH c, max(length(p)) as descendants\n",
    "ORDER BY descendants DESC\n",
    "LIMIT 5\n",
    "RETURN c.name as character,\n",
    "       descendants\n",
    "\n",
    "\"\"\"\n",
    "\n",
    "run_query(oldest_halfelf_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "wOIj6SAGHa4Z"
   },
   "source": [
    "It seems like Dior Eluchíl is the oldest recorded half-elf. I inspected results on LOTR fandom site, and it seems we are correct. Dior Eluchil was born in the First Age in the year 470. There are a couple of other half-elves who were born within 50 years of Dior.\n",
    "\n",
    "### Betweenness centrality\n",
    "\n",
    "We will also take a look at the betweenness centrality algorithm. It is used to find bridge nodes between different communities. If we take a look at the following visualization, we can observe that Captain America has the highest betweenness centrality score. That is because he is the main bridge in the network and connects the left-hand side of the network to the right-hand side. The second bridge in the network is the Beast. We can easily see that all the information exchanged between the main and right-hand side of the network has to go through him to reach the right-hand side.\n",
    "\n",
    "We will look for the bridge characters in the largest family network. My guess would be that spouses in an interracial marriage will come out on top. This is because all the communication between the races flows through them. We've seen that there are only six interracial marriages, so probably some of them will come out on top."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 81
    },
    "id": "VOKMREFFRFMg",
    "outputId": "e5381a6d-ad4a-4b30-c1bb-2139eb4e1c48"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>nodeQuery</th>\n",
       "      <th>relationshipQuery</th>\n",
       "      <th>graphName</th>\n",
       "      <th>nodeCount</th>\n",
       "      <th>relationshipCount</th>\n",
       "      <th>projectMillis</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>MATCH (n:Character) WHERE n.familyComponent = ...</td>\n",
       "      <td>MATCH (s:Character)-[:HAS_FATHER|HAS_MOTHER|SP...</td>\n",
       "      <td>largest-wcc</td>\n",
       "      <td>321</td>\n",
       "      <td>1108</td>\n",
       "      <td>34</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                           nodeQuery  \\\n",
       "0  MATCH (n:Character) WHERE n.familyComponent = ...   \n",
       "\n",
       "                                   relationshipQuery    graphName  nodeCount  \\\n",
       "0  MATCH (s:Character)-[:HAS_FATHER|HAS_MOTHER|SP...  largest-wcc        321   \n",
       "\n",
       "   relationshipCount  projectMillis  \n",
       "0               1108             34  "
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "create_largest_wcc_query = \"\"\"\n",
    "CALL gds.graph.project.cypher('largest-wcc', \n",
    "  'MATCH (n:Character) WHERE n.familyComponent = 0 \n",
    "   RETURN id(n) as id',\n",
    "   'MATCH (s:Character)-[:HAS_FATHER|HAS_MOTHER|SPOUSE|SIBLING]-(t:Character) \n",
    "                       RETURN id(s) as source, id(t) as target',\n",
    "    {validateRelationships: false})\n",
    "\"\"\"\n",
    "\n",
    "run_query(create_largest_wcc_query)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 363
    },
    "id": "Jw7ebYM0Ha4Z",
    "outputId": "45ead7c6-98f3-4392-a2f9-4b7180fedda8"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>character</th>\n",
       "      <th>score</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>Arwen</td>\n",
       "      <td>42750.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>Aragorn</td>\n",
       "      <td>42222.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>Arathorn II</td>\n",
       "      <td>40832.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>Arador</td>\n",
       "      <td>40542.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>Argonui</td>\n",
       "      <td>40248.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>Arathorn I</td>\n",
       "      <td>39950.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>Arassuil</td>\n",
       "      <td>39648.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>Elrond</td>\n",
       "      <td>39371.107143</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>Arahad II</td>\n",
       "      <td>39342.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>Aravorn</td>\n",
       "      <td>39032.000000</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "     character         score\n",
       "0        Arwen  42750.000000\n",
       "1      Aragorn  42222.000000\n",
       "2  Arathorn II  40832.000000\n",
       "3       Arador  40542.000000\n",
       "4      Argonui  40248.000000\n",
       "5   Arathorn I  39950.000000\n",
       "6     Arassuil  39648.000000\n",
       "7       Elrond  39371.107143\n",
       "8    Arahad II  39342.000000\n",
       "9      Aravorn  39032.000000"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "betwenness_centrality_query = \"\"\"\n",
    "\n",
    "CALL gds.betweenness.stream('largest-wcc')\n",
    "YIELD nodeId, score\n",
    "RETURN gds.util.asNode(nodeId).name as character,\n",
    "        score\n",
    "ORDER BY score DESC \n",
    "LIMIT 10\n",
    "\n",
    "\"\"\"\n",
    "\n",
    "run_query(betwenness_centrality_query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "DbZGAkRUHa4a"
   },
   "source": [
    "Interesting to see that Arwen and Aragorn come out on top. Not exactly sure why, but I keep on thinking that they are the modern Romeo and Juliet that have formed an alliance between men and half-elves with their marriage. I have no idea how the JRR Tolkien system for generating names worked, but it seems a bit biased towards names starting with an A."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "zHt9z2yyHa4a"
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "colab": {
   "include_colab_link": true,
   "name": "Part1 Importing Wikidata into Neo4j and analyzing family trees.ipynb",
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
